/* Team 5
 * James McQueen
 * Corwyn Simpson
 * May 1 ish, 2012
 */
package spider.controllers;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import spider.models.Page;
import spider.models.Data;

/**
 * Implements a crude but effective HTML parser that pulls links from the page 
 * and records the number of occurrences of given keywords in a single-use 
 * fashion (meaning that once created, it will only parse the page it was given).
 * The parsing can be done in a single-threaded fashion calling run() directly,
 * or can be wrapped with a standard Java Thread to run concurrently with other
 * instances of this class.
 * @see java.lang.Thread
 * @author James McQueen
 * @author Corwyn Simpson
 */
public class Parser implements Runnable
{    
    /**
     * The Page that this Parser will parse.
     */
    private Page my_page;
    /**
     * The Pattern that will be used to parse my_page.
     */
    private Pattern my_pattern;
    /**
     * The List of new links that were found on this page.
     */
    private List<URL> my_parsed_links;
    /**
     * The list of keywords that this Parser is looking for as it parses.
     */
    private String[] my_keywords;
    /**
     * The count of how many occurences each keyword had on my_page.
     */
    private int[] my_keywords_count;
    /**
     * The number of words that were on the page, not counting those inside of 
     * tags.
     */
    private int my_word_count;

    /**
     * Creates a new Parser that will  parse the_page and keep track of 
     * instances of keywords and any new links to visit
     * @param the_page The Page that needs to be parsed.
     * @param the_keywords An array of the keywords to search for.
     * @param the_search_regex The Pattern to use for parsing the_page. This 
     * Pattern is expected to be a result from ParserGroup.buildRegex(String[])
     * where the parameter for this call is the same as the_keywords. Failure to
     * follow this expectation can result in unexpected behavior in parsing. 
     * If this field is null, a Pattern will be created using 
     * ParserGroup.buildRegex(the_keywords), but the creation of a Pattern each 
     * time is expensive.
     *  
     * @see spider.controllers.ParserGroup.buildRegex(String[])
     */
    public Parser(final Page the_page, final String[] the_keywords, 
                  final Pattern the_search_regex)
    {
        my_page = the_page;
        my_keywords = the_keywords;
        if (the_search_regex == null)
        {
            my_pattern = ParserGroup.buildRegex(the_keywords);
        }

        else
        {
            my_pattern = the_search_regex;
        }
        //create buffers used for results
        my_keywords_count = new int[the_keywords.length];
        my_parsed_links = new ArrayList<URL>();
    }
    
    /**
     * Creates a new Parser with fields populated from the_parser_group. 
     * @throws InterruptedException if the retrieval of a page from 
     * the_parser_group's PageBuffer is interrupted.
     */
    public Parser(final ParserGroup the_parser_group) throws InterruptedException
    {
        this(the_parser_group.getPageBuffer().removePage(),
             the_parser_group.getKeywords(),
             the_parser_group.getSearchRegex());
    }
        
    /**
     * Parses the Page given at construction using the Pattern given at 
     * construction. After this method is called, getPageLinks() and 
     * getPageData() will return a list of Data and links in the page, 
     * respectively.
     */
    public void parsePage()
    {
        String text = my_page.getText();
        Matcher matcher = my_pattern.matcher(text);
        
        while (matcher.find())
        {
            int group_num = 1;
            while (matcher.group(group_num) == null)
            {
                group_num++;
            }
            if (group_num == 1) //the match was a link
            {
                String link = matcher.group(1);
                try
                {
                    String protocol = matcher.group(2);
                    if (protocol == null || protocol.matches("https?:"))
                    { //there is no protocol or the protocol is http
                        my_parsed_links.add(new URL(my_page.getUrl(), link));
                    }
                }
                catch (MalformedURLException e)
                {
                    System.err.println("Invalid link in href: \"" + link +'"');
                }
            }
            else
            {
                my_keywords_count[group_num - ParserGroup.EXTRA_GROUP_COUNT]++;
            }
        }
        
        my_word_count = text.split("\\s+?|\\s*<.*?>\\s*").length;
    }
    
    /**
     * Returns a list of Data populated with the current URL, a keyword from the
     * keywords list, and how many times it appeared. Additionally, an extra 
     * Data is created with the special string "*" which represents total word
     * count.
     */
    public List<Data> getPageData()
    {
        List<Data> results = new ArrayList<Data>();
        for(int i = 0; i < my_keywords_count.length; i++)
        {
            results.add(new Data(my_page.getUrl(), my_keywords[i],
                            my_keywords_count[i]));
        }
        results.add(new Data(my_page.getUrl(), "*", my_word_count));
        return results;
    }
    
    /**
     * Returns a list of URLs that were found while parsing the page. 
     */
    public List<URL> getPageLinks()
    {
        return my_parsed_links;
    }

    /**
     * Just runs parsePage() on this Parser.
     */
    @Override
    public void run()
    {
        parsePage();
    }
}